import { _decorator, Component, Node, Sprite, Renderable2D, spriteAssembler, RenderData, gfx, Color, Vec2, v2, UITransform, Texture2D, RenderTexture, find, Material, Camera, SpriteFrame, Layers, color, director } from 'cc';
import PageEffectAssembler, { IBatcher, TextureBase } from './PageEffectAssembler';
const { ccclass, property } = _decorator;

class PagePoint {
    public oldPos: Vec2;
    public newPos: Vec2;

    constructor(x: number, y: number) {
        this.oldPos = v2(x, y);
        this.newPos = v2(x, y);
    }
}

@ccclass('PageRender')
export class PageRender extends Renderable2D {
    @property({ type: [Texture2D], displayName: "纹理" })
    textureList: Texture2D[] = [];
    @property({ displayName: "每条边上的顶点数量" })
    pointsCount: number = 30;
    @property({ displayName: "纠正次数" })
    constraintTimes: number = 100;
    @property({ displayName: "速度衰减系数" })
    damping: number = 0.1;
    @property({ displayName: "重力" })
    gravity: number = 0;

    uiTransform: UITransform;

    private m_Angle: number = 0;
    private m_Add = true;

    private m_Gravity = v2();
    private m_PointList: PagePoint[] = [];
    private m_PointList0: Vec2[] = [];

    private m_Vec0 = v2();
    private m_Vec1 = v2();

    onEnable() {
        this.uiTransform = this.getComponent(UITransform);

        this._assembler = new PageEffectAssembler(this);
        this.initPointList();

        this.setTexture("texture1", this.textureList[1]);
        // this.setTexture("texture0", this.textureList[0]);
        this.scheduleOnce(() => {
            this.setTexture("texture0", PageRender.screenshot(find("Canvas/rt0")));
        })
    }

    public setTexture(name: "texture0" | "texture1", texture: TextureBase) {
        const material = this.getMaterial(0);
        material.setProperty(name, texture);
    }

    initData(data: RenderData) {
        this._renderData = data;
        this.updateMaterial();
    }

    protected _render(render: IBatcher) {
        render.commitComp(this, this.renderData, null, this._assembler, null);
    }

    update(dt: number) {
        this.simulate();
        this.applyConstraint();
        this.markForUpdateRenderData(true);

        if (this.m_Add) {
            if (this.m_Angle < 180) {
                this.m_Angle += 2;
            } else {
                this.m_Angle = 180;
                this.m_Add = false;
            }
        } else {
            if (this.m_Angle > 0) {
                this.m_Angle -= 2;
            } else {
                this.m_Angle = 0;
                this.m_Add = true;
            }
        }
    }

    /**
     * 获取所有质点位置
     */
    public getPointList() {
        const pointList = this.m_PointList;
        const pointList0 = this.m_PointList0;
        for (let g = 0, n = pointList.length; g < n; g++) {
            const pos = pointList[g].newPos;
            pointList0[g].set(pos);
        }

        return pointList0;
    }

    /**
     * 使用 verlet 积分更新位置
     */
    public simulate() {
        const gravity = this.m_Gravity;
        gravity.set(0, this.gravity);

        const pointList = this.m_PointList;
        const v1 = this.m_Vec1;
        const damping = this.damping;
        for (let i = this.pointsCount - 1; i >= 1; i--) {
            let point = pointList[i];
            let newPos = point.newPos;
            // 速度等于当前位置与上一个位置的差乘上衰减系数
            v1.set(newPos);
            let velocity = v1.subtract(point.oldPos).multiplyScalar(damping);
            // 模拟一个水平放置的绳子，当y小于等于 0 时，将不再受重力影响
            if (newPos.y <= 0) {
                gravity.y = Math.max(0, gravity.y);
            }
            point.oldPos.set(newPos);
            newPos.add(velocity)
            newPos.add(gravity)
        }
    }

    /**
     * 约束纠正
     */
    private applyConstraint() {
        // 两个质点之间的固定距离
        const constraintTimes = this.constraintTimes;
        const pointsCount = this.pointsCount;
        const normalDistance = this.uiTransform.width / (pointsCount - 1);
        const v1 = this.m_Vec1;
        const endPos = this.getEndPos();
        const pointList = this.m_PointList;
        for (let t = 0; t < constraintTimes; t++) {
            this.updateEndPos(endPos);
            // 由最后一个质点开始依次纠正
            for (let i = pointsCount - 1; i >= 1; i--) {
                let firstPoint = pointList[i - 1];
                let secondPoint = pointList[i];
                v1.set(secondPoint.newPos);
                let delatPos = v1.subtract(firstPoint.newPos);
                let distance = delatPos.length();
                let fixDirection: Vec2 = null;
                if (distance < normalDistance) {
                    fixDirection = delatPos.normalize().negative();
                } else if (distance > normalDistance) {
                    fixDirection = delatPos.normalize();
                } else {
                    continue;
                }

                let fixLen = Math.abs(distance - normalDistance);
                if (i == 1) {
                    // 由于第一个质点是固定的，所以只对第二个质点做纠正
                    let fixVector = fixDirection.multiplyScalar(fixLen);
                    secondPoint.newPos.subtract(fixVector);
                } else {
                    // 将两个质点之间的距离纠正为固定长度
                    let fixHalfVector = fixDirection.multiplyScalar(fixLen * 0.5);
                    firstPoint.newPos.add(fixHalfVector);
                    secondPoint.newPos.subtract(fixHalfVector);
                }
            }
        }
    }

    /**
     * 初始化质点
     */
    private initPointList() {
        const width = this.uiTransform.width;
        const pointList = this.m_PointList;
        const pointList0 = this.m_PointList0;
        for (let i = 0, n = this.pointsCount; i < n; ++i) {
            let posX = i / (n - 1) * width;
            pointList.push(new PagePoint(posX, 0));
            pointList0.push(v2());
        }
    }

    private updateEndPos(endPos: Vec2) {
        let tailPoint = this.m_PointList[this.pointsCount - 1];
        tailPoint.newPos.set(endPos);
    }

    private getEndPos() {
        const endPos = this.m_Vec0;
        const width = this.uiTransform.width;
        const angle = this.m_Angle;
        const rad = angle * Math.PI / 180;

        // 与贝塞尔曲线使用相同的运动轨迹
        let per = rad * 2 / Math.PI;
        if (angle <= 90) {
            const endPosX = width * (1 - Math.pow(per, 3));
            const endPosY = width * 1 / 4 * (1 - Math.pow(1 - per, 4));
            endPos.set(endPosX, endPosY);
        } else {
            per = per - 1;
            const endPosX = - width * (1 - Math.pow(1 - per, 3));
            const endPosY = width * 1 / 4 * (1 - Math.pow(per, 4));
            endPos.set(endPosX, endPosY);
        }
        return endPos;
    }

    public static screenshot(node: Node) {
        const uiTransform = node.getComponent(UITransform);
        const { width, height } = uiTransform;

        const rt = new RenderTexture();
        rt.reset({ width: width, height: height });

        let camera = node.getComponent(Camera);
        if (!camera) camera = node.addComponent(Camera);
        camera.visibility = Layers.Enum["DEPTH"];
        camera.clearColor = color(0, 0, 0, 0);
        camera.projection = Camera.ProjectionType.ORTHO;
        camera.orthoHeight = height * 0.5;
        camera.near = 0;
        camera.targetTexture = rt;

        director.root.frameMove(0);

        camera.targetTexture = null;
        camera.destroy();

        return rt;
    }
}

